library pp_net;

import 'dart:convert';
import 'dart:io';

import 'package:dio/dio.dart';
import 'package:dio/io.dart';
import 'package:dio_smart_retry/dio_smart_retry.dart';
import 'package:flutter/foundation.dart';
import 'package:http_proxy/http_proxy.dart';
import 'package:pp_net/pp_bean_factory.dart';
import 'package:pp_net/pp_response.dart';

part 'pp_exception.dart';
part 'pp_net_config.dart';
part 'pp_req.dart';

// 定义两个函数
typedef Success<T> = Function(T data); // 请求数据data为obj对象
typedef SuccessList<T> = Function(List<T> dataArray); // 请求数据data为[]数组
typedef Failure = Function(PPException error);
typedef Completed = Function(PPResponseSet result);
typedef MapData = Map<dynamic, dynamic>;

class PPNet {
  late Dio _dio;

  static final Dio dio = PPNet._instance._dio;

  static final PPNet _instance = PPNet._init();

  factory PPNet() => _instance;

  /// 创建 dio 实例对象
  PPNet._init() {
    // 全局属性：请求前缀、连接超时时间、响应超时时间
    BaseOptions options = BaseOptions(
      baseUrl: PPNetConfig.instance.baseUrl,
      connectTimeout: const Duration(seconds: 10),
      receiveTimeout: const Duration(seconds: 10),
      headers: {
        "user-agent": PPNetConfig.instance.userAgent,
      },
    );
    _dio = Dio(options);

    //设置代理
    if (PPNetConfig.instance.isDebug) {
      _setupProxy(_dio);
    }
  }

  static Future<void> install() async {
    PPNet()._dio.options.baseUrl = PPNetConfig.instance.baseUrl;
    if (PPNetConfig.instance.userAgent != null) {
      PPNet()._dio.options.headers["user-agent"] =
          PPNetConfig.instance.userAgent;
    }
    PPNet()._dio.interceptors.addAll(PPNetConfig.instance._interceptors);
    //设置代理
    if (PPNetConfig.instance.isDebug) {
      await _setupProxy(PPNet()._dio);
    }
  }

  static Future<void> _setupProxy(Dio dio) async {
    HttpProxy httpProxy = await HttpProxy.createHttpProxy();
    var proxyHost = httpProxy.host;
    var proxyPort = httpProxy.port;
    debugPrint(
        "[PPNet] _setupProxy createHttpProxy proxyHost=$proxyHost proxyPort=$proxyPort");
    proxyHost ??= PPNetConfig.instance.proxyAddress;
    proxyPort ??= "8888";

    // 优先自定义设置的代理
    if (PPNetConfig.instance.proxyAddress != null) {
      proxyHost = PPNetConfig.instance.proxyAddress;
      proxyPort = "8888";
      debugPrint(
          "[PPNet] _setupProxy 设置自定义代理 proxyHost=$proxyHost proxyPort=$proxyPort");
    }
    if (proxyHost == null) return;
    dio.httpClientAdapter = IOHttpClientAdapter(
      createHttpClient: () {
        final client = HttpClient();
        client.findProxy = (uri) {
          debugPrint("[PPNet] _setupProxy 代理 $proxyHost:$proxyPort;");
          return "PROXY $proxyHost:$proxyPort;";
        };
        client.badCertificateCallback =
            (X509Certificate cert, String host, int port) => true;
        return client;
      },
    );
  }

  static Future<Response> _convertToDio(Dio dio, PPReq req) {
    var method = 'POST';
    if (req.method == PPRequestMethod.GET) {
      method = 'GET';
    } else if (req.method == PPRequestMethod.PUT) {
      method = 'PUT';
    }

    var options = Options();
    options.method = method;
    if (req.headers != null) {
      options.headers = req.headers;
    }
    if (req.retries > 0) {
      var extra = Map.of(options.extra ??= <String, dynamic>{});
      extra = {
        kRetriesKey: req.retries,
        kRetryDelayKey: req.retryDelay,
        kSuccessCodeKey: PPNetConfig.instance.successCode,
      };
      options.extra = extra;
    }

    // GET
    if (req.method == PPRequestMethod.GET) {
      return dio.request(
        req.path,
        queryParameters: req.parameters,
        options: options,
      );
    } else if (req.method == PPRequestMethod.PUT) {
      return dio.put(
        req.path,
        data: req.data,
        options: options,
      );
    } else {
      // POST
      dynamic tData = req.data ?? req.parameters;
      return dio.request(
        req.path,
        data: tData,
        options: options,
      );
    }
  }

  static void send<T>(PPReq req,
      {Success<T>? success,
      SuccessList<T>? successList,
      Failure? failure}) async {
    try {
      Dio dio = PPNet()._dio;
      var response = await _convertToDio(dio, req);
      var data = jsonDecode(response.toString());
      PPResponse<T> result = PPResponse<T>.fromJson(data);
      if (result.code == PPNetConfig.instance.successCode) {
        if (result.dataArray != null) {
          if (successList != null) {
            successList(result.dataArray!);
          } else {
            debugPrint("网络请求，返回数据为数组，但是 successList回调 没有实现");
          }
        } else {
          if (success != null) {
            if (result.data != null) {
              success(result.data as T);
            } else {
              if (failure != null) {
                failure(PPException(result.code!, "empty data！！"));
              }
            }
          } else {
            debugPrint("网络请求，返回数据为Model，但是 success回调 没有实现");
          }
        }
      } else {
        var exception = result.exception;
        // ignore: prefer_conditional_assignment
        if (null == exception) {
          exception = PPException(result.code!, result.message ??= "");
        }
        if (failure != null) {
          failure(exception);
        }
      }
    } catch (e) {
      // if (req.retries > 0) {

      // }
      if (failure != null) {
        if (e is DioException) {
          failure(PPException.create(e));
        } else {
          failure(PPException(-1, e.toString()));
        }
      }
    }
  }

  // 发起多个并发请求
  static void sendGroup(List<PPReq> reqs, Completed complete,
      {Failure? failure}) async {
    if (reqs.isEmpty) {
      return;
    }
    try {
      Dio dio = PPNet()._dio;
      List<Future<Response<dynamic>>> list = <Future<Response<dynamic>>>[];
      for (var req in reqs) {
        list.add(_convertToDio(dio, req));
      }
      var response = await Future.wait(list);
      var result = PPResponseSet();
      for (var item in response) {
        result.addData(item);
      }

      complete(result);
    } on DioException catch (error) {
      if (failure != null) {
        failure(PPException.create(error));
      }
    } catch (e) {
      debugPrint('sendGroup error = $e');
      if (failure != null) {
        failure(PPException(-1, e.toString()));
      }
    }
  }

  static void upload(PPReq req,
      {Function()? success, Function(String error)? failure}) async {
    Dio dio = PPNet()._dio;
    try {
      var response = await _convertToDio(dio, req);
      if (response.statusCode == 200) {
        if (success != null) {
          success();
        }
      } else {
        if (failure != null) {
          failure("upload failure, try again (${response.statusCode})");
        }
      }
    } on DioException catch (error) {
      var message = PPException.create(error).message;

      if (failure != null) {
        failure("$message ${error.type}");
      }
    } catch (e) {
      if (failure != null) {
        failure("upload failure, try again e:($e)");
      }
    }
  }

  static void get(
    String url, {
    Map<String, dynamic>? parameters,
    Function(dynamic data)? success,
    Failure? failure,
  }) async {
    try {
      Response response;
      var headers = {"user-agent": PPNetConfig.instance.userAgent};
      if (Platform.isIOS) {
        headers["platform"] = "iOS";
      } else {
        headers["platform"] = "android";
      }
      headers["versioncode"] = PPNetConfig.instance.buildNumber;
      var options = BaseOptions(
        connectTimeout: const Duration(seconds: 10),
        receiveTimeout: const Duration(seconds: 3),
        headers: headers,
      );
      var dio = Dio(options);
      for (var interceptor in PPNetConfig.instance._interceptors) {
        dio.interceptors.add(interceptor);
      }

      //设置代理
      if (PPNetConfig.instance.isDebug) {
        _setupProxy(dio);
      }
      response = await dio.get(url, queryParameters: parameters);
      if (success != null) {
        success(response.data);
      }
    } catch (e) {
      if (failure != null) {
        if (e is DioException) {
          failure(PPException.create(e));
        } else {
          failure(PPException(-1, e.toString()));
        }
      }
    }
  }

  static void post(
    String url, {
    Map<String, dynamic>? parameters,
    Function(dynamic data)? success,
    Failure? failure,
  }) async {
    try {
      Response response;
      var headers = {"user-agent": PPNetConfig.instance.userAgent};
      if (Platform.isIOS) {
        headers["platform"] = "iOS";
      } else {
        headers["platform"] = "android";
      }
      headers["versioncode"] = PPNetConfig.instance.buildNumber;
      var options = BaseOptions(
        connectTimeout: const Duration(seconds: 10),
        receiveTimeout: const Duration(seconds: 3),
        headers: headers,
      );
      var dio = Dio(options);
      for (var interceptor in PPNetConfig.instance._interceptors) {
        dio.interceptors.add(interceptor);
      }

      //设置代理
      if (PPNetConfig.instance.isDebug) {
        _setupProxy(dio);
      }

      // POST
      response = await dio.request(
        url,
        data: parameters,
        options: Options(
          method: 'POST',
          headers: headers,
        ),
      );
      if (success != null) {
        success(response.data);
      }
    } catch (e) {
      if (failure != null) {
        if (e is DioException) {
          failure(PPException.create(e));
        } else {
          failure(PPException(-1, e.toString()));
        }
      }
    }
  }

  static void convertPhpData<T>(Map<String, dynamic>? response, String cmd,
      {Success<T>? success, SuccessList<T>? successList, Failure? failure}) {
    if (response != null) {
      var cmdData = response[cmd];
      if (cmdData != null) {
        var code = cmdData["statusCode"] as int;
        if (code == 0) {
          var data = cmdData["data"];
          if (data != null) {
            if (data is List) {
              if (successList != null) {
                successList(PPBeanFactory.generateModelArray<T>(data)!);
              }
            } else {
              if (success != null) {
                success(PPBeanFactory.generateModel<T>(data) as T);
              }
            }
          } else {
            if (failure != null) {
              failure(PPException(-1, "没有相关数据"));
            }
          }
        } else {
          if (failure != null) {
            failure(PPException(code, "${cmdData["errorMsg"]}"));
          }
        }
      } else {
        if (failure != null) {
          failure(PPException(-1, "没有相关数据"));
        }
      }
    } else {
      if (failure != null) {
        failure(PPException(-1, "没有相关数据"));
      }
    }
  }
}
